package com.lyf.scm.plan.domain.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageInfo;
import com.lyf.scm.common.constants.BigDataEsConstants;
import com.lyf.scm.common.enums.EsConmulEnum;
import com.lyf.scm.common.enums.EsSumColumnEnum;
import com.lyf.scm.common.enums.SalesOtherTypeEnum;
import com.lyf.scm.common.enums.SkuUnitTypeEnum;
import com.lyf.scm.plan.api.dto.*;
import com.lyf.scm.plan.domain.remote.basedata.dto.ChannelDTO;
import com.lyf.scm.plan.domain.remote.basedata.dto.StoreDTO;
import com.lyf.scm.plan.domain.remote.item.dto.SkuUnitDTO;
import com.lyf.scm.plan.domain.repository.SalesPlanEsRepository;
import com.lyf.scm.plan.domain.service.RedisCacheService;
import com.lyf.scm.plan.domain.service.SalesPlanEsService;
import com.lyf.scm.plan.insfrastructure.db.dataobject.es.SalesPlanEsEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.search.aggregations.bucket.composite.CompositeAggregation;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.metrics.NumericMetricsAggregation;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * description:
 * ES查询Service
 *
 * @author: haodong
 * @data: 2020/11/5 21:00
 */
@Slf4j
@Service
public class SalesPlanEsServiceImpl implements SalesPlanEsService {

    @Resource
    private SalesPlanEsRepository salesPlanEsRepository;
    @Resource
    private RedisCacheService redisCacheService;

    @Override
    public PageInfo<EsReturnDTO> queryByGroup(EsAggregationDTO esAggregation) {
        // 查询结果
        AggregatedPage<SalesPlanEsEntity> esEntities = salesPlanEsRepository.queryByGroup(esAggregation);
        if (esEntities == null) {
            return null;
        }
        // 解析es返回结果
        PageInfo<EsReturnDTO> pageInfo = parsingData(esEntities, esAggregation);
        // 换算单位
        List<EsReturnDTO> esReturnList = conversionUnit(pageInfo.getList(), esAggregation.getUnitType());
        pageInfo.setList(esReturnList);
        return pageInfo;
    }

    @Override
    public List<SalesPlanEsEntity> listPageQuery(EsQueryDTO esQuery, Integer pageNum, Integer pageSize) {
        return salesPlanEsRepository.listPageQuery(esQuery, pageNum, pageSize);
    }

    @Override
    public Boolean update(List<SalesPlanEsEntity> salesPlanEsEntities) {
        List<IndexQuery> indexQueryList = new ArrayList<>();
        for (SalesPlanEsEntity salesPlanEsEntity : salesPlanEsEntities) {
            IndexQuery indexQueryBuilder = new IndexQueryBuilder().withObject(salesPlanEsEntity).build();
            indexQueryList.add(indexQueryBuilder);
        }
        return salesPlanEsRepository.update(indexQueryList);
    }

    /**
     * 导出查询
     *
     * @param esExportAggregationDTO
     * @return {@link EsScrollReturnDTO}
     **/
    @Override
    public EsScrollReturnDTO exportByGroup(EsExportAggregationDTO esExportAggregationDTO) {
        // 查询结果
        AggregatedPage<SalesPlanEsEntity> esEntities = salesPlanEsRepository.exportByGroup(esExportAggregationDTO);
        if (esEntities == null) {
            return new EsScrollReturnDTO();
        }
        // 解析es返回结果
        EsScrollReturnDTO esScrollReturnDTO = parsingExportData(esEntities, esExportAggregationDTO);
        // 换算单位
        List<EsReturnDTO> esReturnList = conversionUnit(esScrollReturnDTO.getEsReturnDTOList(), esExportAggregationDTO.getUnitType());
        esScrollReturnDTO.setEsReturnDTOList(esReturnList);
        return esScrollReturnDTO;
    }

    /**
     * 解析ES返回结果
     *
     * @param entities
     * @param esAggregation
     * @return
     */
    private PageInfo<EsReturnDTO> parsingData(AggregatedPage<SalesPlanEsEntity> entities, EsAggregationDTO esAggregation) {
        PageInfo<EsReturnDTO> pageInfo = new PageInfo<>();
        List<String> groupColumns = esAggregation.getGroupColumns();
        List<String> sumColumns = esAggregation.getSumColumns();
        if (CollectionUtils.isNotEmpty(groupColumns)) {
            StringTerms stringTerms = (StringTerms) entities.getAggregation(BigDataEsConstants.GROUP_DATA);
            //分页
            List<StringTerms.Bucket> buckets = pagination(pageInfo, esAggregation, stringTerms.getBuckets());
            // 解析
            List<EsReturnDTO> resultList = buckets.stream().map(val -> {
                // 初始化返回参数
                EsReturnDTO esReturn = new EsReturnDTO();
                // 解析列
                analyzeColumns(esReturn, groupColumns, val);

                StringTerms dateStringTerms = val.getAggregations().get(BigDataEsConstants.GROUP_DATE);
                // 解析指标
                Map<String, List<BigDecimal>> targetMap =
                        analyzeTarget(dateStringTerms, sumColumns, esAggregation.getDateList());
                esReturn.setTargetMap(targetMap);
                return esReturn;
            }).collect(Collectors.toList());
            // 设置结果集
            pageInfo.setList(resultList);
            return pageInfo;
        } else {
            StringTerms dateStringTerms = (StringTerms) entities.getAggregation(BigDataEsConstants.GROUP_DATE);
            // 解析指标
            Map<String, List<BigDecimal>> targetMap =
                    analyzeTarget(dateStringTerms, sumColumns, esAggregation.getDateList());

            EsReturnDTO esReturn = new EsReturnDTO();
            esReturn.setTargetMap(targetMap);
            // 设置结果集
            pageInfo.setList(Collections.singletonList(esReturn));
            return pageInfo;
        }
    }

    /**
     * 解析ES返回结果
     *
     * @param entities
     * @param esExportAggregationDTO
     * @return
     */
    private EsScrollReturnDTO parsingExportData(AggregatedPage<SalesPlanEsEntity> entities, EsExportAggregationDTO esExportAggregationDTO) {
        EsScrollReturnDTO esScrollReturnDTO = new EsScrollReturnDTO();
        List<String> groupColumns = esExportAggregationDTO.getGroupColumns();
        List<String> sumColumns = esExportAggregationDTO.getSumColumns();
        CompositeAggregation parsedComposite = entities.getAggregations().get(BigDataEsConstants.GROUP_PRI);
        if (parsedComposite.afterKey() == null) {
            return esScrollReturnDTO;
        }
        esScrollReturnDTO.setAfterKey(parsedComposite.afterKey().get(BigDataEsConstants.GROUP_MULTI).toString());
        List<? extends CompositeAggregation.Bucket> list = parsedComposite.getBuckets();
        List<EsReturnDTO> esReturnDTOList = new ArrayList<>();
        EsReturnDTO esReturn;
        for (CompositeAggregation.Bucket parsedBucket : list) {
            // 初始化返回参数
            esReturn = new EsReturnDTO();
            // 解析列
            exportAnalyzeColumns(esReturn, groupColumns, parsedBucket);
            // 解析指标
            Map<String, List<BigDecimal>> targetMap =
                    analyzeTarget((StringTerms) parsedBucket.getAggregations().asMap().get(BigDataEsConstants.GROUP_DATE), sumColumns, esExportAggregationDTO.getDateList());
            esReturn.setTargetMap(targetMap);
            esReturnDTOList.add(esReturn);
        }
        if (list.size() < BigDataEsConstants.EXPORT_EACH_LENGTH) {
            esScrollReturnDTO.setAfterKey(null);
        }
        esScrollReturnDTO.setEsReturnDTOList(esReturnDTOList);
        return esScrollReturnDTO;
    }

    private Map<String, List<BigDecimal>> analyzeTarget(StringTerms dateStringTerms, List<String> sumColumns, List<String> dateList) {
        // 小数位保留长度为3
        final int tLength = 3;
        Map<String, List<BigDecimal>> targetMap = new HashMap<>();
        for (String column : sumColumns) {
            // 返回在每个时间内的同一个指标
            Map<String, BigDecimal> sumMap = dateStringTerms.getBuckets().stream()
                    .collect(Collectors.toMap(StringTerms.Bucket::getKeyAsString,
                            sum -> {
                                NumericMetricsAggregation.SingleValue aggregation = sum.getAggregations().get(column);
                                return Double.isNaN(aggregation.value()) ? BigDecimal.ZERO : new BigDecimal(aggregation.getValueAsString());
                            }));
            // 根据时间排序add到list中
            List<BigDecimal> targetList = new ArrayList<>();
            dateList.forEach(date -> {
                BigDecimal target = sumMap.keySet().stream().filter(date::equals)
                        .map(sumMap::get).findFirst().orElse(BigDecimal.ZERO);
                target = target.scale() > tLength ? target.setScale(tLength, BigDecimal.ROUND_HALF_UP) : target;
                targetList.add(target);
            });
            targetMap.put(column, targetList);
        }
        return targetMap;
    }

    private void analyzeColumns(EsReturnDTO esReturn, List<String> groupColumns, StringTerms.Bucket val) {
        // 解析key
        String keyAsString = val.getKeyAsString();
        String[] split = keyAsString.split("&&");
        int length = split.length;
        for (int i = 0; i < length; i++) {
            if (length == 1) {
                setColumns(esReturn, groupColumns.get(i), split[i]);
            } else {
                setColumns(esReturn, groupColumns.get(i), split[i].substring(1, split[i].length() - 1));
            }
        }
    }

    private void exportAnalyzeColumns(EsReturnDTO esReturn, List<String> groupColumns, CompositeAggregation.Bucket parsedBucket) {
        // 解析key
        String keyAsString = parsedBucket.getKey().get(BigDataEsConstants.GROUP_MULTI).toString();
        String[] split = keyAsString.split("&&");
        int length = split.length;
        for (int i = 0; i < length; i++) {
            if (length == 1) {
                setColumns(esReturn, groupColumns.get(i), split[i]);
            } else {
                setColumns(esReturn, groupColumns.get(i), split[i].substring(1, split[i].length() - 1));
            }
        }
    }

    private List<StringTerms.Bucket> pagination(PageInfo<EsReturnDTO> pageInfo, EsAggregationDTO esAggregation, List<StringTerms.Bucket> buckets) {
        pageInfo.setTotal(buckets.size());
        // 分页
        Integer pageNum = esAggregation.getPageNum();
        Integer pageSize = esAggregation.getPageSize();
        if (pageNum != null && pageSize > 0 && buckets.size() > pageSize) {
            pageNum = pageSize * (pageNum - 1);
            buckets = buckets.stream().skip(pageNum).limit(pageSize).collect(Collectors.toList());
            pageInfo.setPageNum(esAggregation.getPageNum());
            pageInfo.setPageSize(buckets.size());
        }
        return buckets;
    }

    private void setColumns(EsReturnDTO info, String column, String target) {
        if (column.equals(EsConmulEnum.CHANNEL_KEYWORD.getFName())) {
            ChannelDTO channelInfo = redisCacheService.getAllChannelInfo().get(target);
            info.setChannel(target);
            info.setChannelName(channelInfo.getChannelName());
            return;
        }
        if (column.equals(EsConmulEnum.PROVINCE_CODE_KEYWORD.getFName())) {
            getAreaInfo(info, target, 1);
            return;
        }
        if (column.equals(EsConmulEnum.CITY_CODE_KEYWORD.getFName())) {
            getAreaInfo(info, target, 1);
            return;
        }
        if (column.equals(EsConmulEnum.COUNTY_CODE_KEYWORD.getFName())) {
            getAreaInfo(info, target, 1);
            return;
        }
        if (column.equals(EsConmulEnum.STORE_KEYWORD.getFName())) {
            getAreaInfo(info, target, 2);
            return;
        }
        if (column.equals(EsConmulEnum.WAREHOUSE_KEYWORD.getFName())) {
            StoreDTO warehouseInfo = redisCacheService.getDeliverWarehouse().get(target);
            info.setDeliverName(warehouseInfo.getName());
            info.setDeliverId(target);
            return;
        }
        if (column.equals(EsConmulEnum.PLAN_TYPE.getFName())) {
            info.setPlanType(target);
            info.setPlanTypeName(SalesOtherTypeEnum.getNameByType(target));
            return;
        }
        if (column.equals(EsConmulEnum.SKU_KEYWORD.getFName())) {
            BaseSkuInfoDTO baseSkuInfo =
                    redisCacheService.getCategoryInfoBySkuCodes(Collections.singletonList(target)).get(target);
            info.setCategory(baseSkuInfo.getCategory());
            info.setSkuCode(baseSkuInfo.getSkuCode());
            info.setSkuName(baseSkuInfo.getSkuName());
        }
    }

    private void getAreaInfo(EsReturnDTO info, String area, Integer type) {
        if (StringUtils.isEmpty(area)) {
            return;
        }
        BaseAreaInfoDTO areaInfo = redisCacheService.getAreaInfo(Collections.singletonList(area), type).get(area);
        if (ObjectUtil.isEmpty(areaInfo)) {
            return;
        }
        info.setArea(areaInfo.getAreaNameSplicing());
        info.setAreaCode(areaInfo.getAreaCodeSplicing());
    }


    /**
     * 换算比例
     *
     * @param paramList
     * @param unitType
     * @return List<EsReturnDTO>
     * @author heray
     * @date 2020/11/13
     **/
    private List<EsReturnDTO> conversionUnit(List<EsReturnDTO> paramList, Long unitType) {
        if(CollectionUtils.isEmpty(paramList)){
            return null;
        }
        List<String> skuCodeList = paramList.stream().filter(e -> StringUtils.isNotEmpty(e.getSkuCode()))
                .map(EsReturnDTO::getSkuCode).distinct().collect(Collectors.toList());
        if (CollectionUtils.isEmpty(skuCodeList) || unitType == null) {
            return paramList;
        }
        Long baseUnit = SkuUnitTypeEnum.BASIS_UNIT.getUnitType();
        if (!unitType.equals(baseUnit)) {
            List<UnitRateQueryDTO> unitQueryList = skuCodeList.stream().map(sku -> {
                UnitRateQueryDTO unit = new UnitRateQueryDTO();
                unit.setSkuCode(sku);
                unit.setFromUnitType(baseUnit);
                unit.setToUnitType(unitType);
                return unit;
            }).collect(Collectors.toList());
            Map<String, SkuUnitDTO> unitRatio = redisCacheService.getUnitConversionRatio(unitQueryList);
            final int tLength = 3;
            return paramList.stream().map(val -> {
                SkuUnitDTO unitInfo = unitRatio.get(val.getSkuCode() + baseUnit + unitType);
                if (ObjectUtils.isEmpty(unitInfo)) {
                    return val;
                }
                val.setUnitCode(unitInfo.getUnitCode());
                val.setUnitName(unitInfo.getUnitName());
                BigDecimal scale = BigDecimal.valueOf(unitInfo.getScale());
                Map<String, List<BigDecimal>> targetMap = val.getTargetMap();
                Map<String, List<BigDecimal>> newTargetMap = new HashMap<>();
                for (String key : targetMap.keySet()) {
                    List<BigDecimal> newTargetList = new ArrayList<>();
                    List<String> notConversionTarget = EsSumColumnEnum.getNotConversionTarget();
                    if (notConversionTarget.contains(key)) {
                        newTargetMap.put(key, targetMap.get(key));
                        continue;
                    }
                    targetMap.get(key).forEach(v -> {
                        BigDecimal newTarget = v.multiply(scale);
                        newTarget = newTarget.scale() > tLength ? newTarget.setScale(tLength, BigDecimal.ROUND_HALF_UP) : newTarget;
                        newTargetList.add(newTarget);
                    });
                    newTargetMap.put(key, newTargetList);
                }
                val.setTargetMap(newTargetMap);
                return val;
            }).collect(Collectors.toList());
        }
        return paramList;
    }

}
